/*
 * Copyright (c) 2010-2022 Belledonne Communications SARL.
 *
 * This file is part of mediastreamer2
 * (see https://gitlab.linphone.org/BC/public/mediastreamer2).
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#ifdef _WIN32
#include <malloc.h> // for alloca
#endif
#include <stdint.h>

#include "genericplc.h"
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msticker.h>

#ifdef HAVE_SPANDSP
/*use the G722 version from spandsp, LGPL */
#include <spandsp.h>
#else
/*otherwise use built-in version, forked from spandsp a long time ago and that has a public license.*/
#include "g722.h"
#endif

#define ENABLE_PCM_RESCALING 1

struct EncState {
	g722_encode_state_t *state;
	uint32_t ts;
	int ptime;
	int maxptime;
	MSBufferizer *bufferizer;
};

static void enc_init(MSFilter *f) {
	struct EncState *s = ms_new0(struct EncState, 1);
	s->state = g722_encode_init(NULL, 64000, 0);
	s->ts = 0;
	s->bufferizer = ms_bufferizer_new();
	s->ptime = 20;
	s->maxptime = MS_DEFAULT_MAX_PTIME;
	f->data = s;
};

static void enc_uninit(MSFilter *f) {
	struct EncState *s = (struct EncState *)f->data;
	g722_encode_release(s->state);
	ms_bufferizer_destroy(s->bufferizer);
	ms_free(s);
	f->data = 0;
};

static void scale_down(int16_t *samples, int count) {
#if ENABLE_PCM_RESCALING
	int i;
	for (i = 0; i < count; ++i)
		samples[i] = samples[i] >> 2;
#endif
}

static void scale_up(int16_t *samples, int count) {
#if ENABLE_PCM_RESCALING
	int i;
	for (i = 0; i < count; ++i) {
		if (samples[i] > INT16_MAX / 4) {
			samples[i] = INT16_MAX;
		} else if (samples[i] < INT16_MIN / 4) {
			samples[i] = INT16_MIN;
		} else {
			samples[i] = samples[i] << 2;
		}
	}
#endif
}

static void enc_process(MSFilter *f) {
	struct EncState *s = (struct EncState *)f->data;
	mblk_t *im;
	int nbytes;
	uint8_t *buf;
	int frame_per_packet = 1;
	size_t chunksize;

	if (s->ptime >= 10) frame_per_packet = s->ptime / 10;

	if (frame_per_packet <= 0) frame_per_packet = 1;

	nbytes = 160 * 2; //  10 Msec at 16KHZ  = 320 bytes of data
	buf = (uint8_t *)alloca(nbytes * frame_per_packet);

	while ((im = ms_queue_get(f->inputs[0])))
		ms_bufferizer_put(s->bufferizer, im);

	chunksize = nbytes * frame_per_packet;
	while (ms_bufferizer_read(s->bufferizer, buf, chunksize) == chunksize) {
		mblk_t *om = allocb(nbytes * frame_per_packet, 0); // too large...
		int k;

		scale_down((int16_t *)buf, (int)(chunksize / 2));
		k = g722_encode(s->state, om->b_wptr, (int16_t *)buf, (int)(chunksize / 2));
		om->b_wptr += k;
		ms_bufferizer_fill_current_metas(s->bufferizer, om);
		mblk_set_timestamp_info(om, s->ts);
		ms_queue_put(f->outputs[0], om);
		/* Nr of samples is really chunksize/2 but for G722 we must pretend we have a 8KHZ sampling rate */
		s->ts += (uint32_t)(chunksize / 4);
	}
};

static void set_ptime(struct EncState *s, int value) {
	ms_message("MSG722Enc want ptime=%i", value);
	s->ptime = MIN(value, s->maxptime);
	if (s->ptime == s->maxptime) ms_message("MSG722Enc ptime set to maxptime=%i", s->maxptime);
}

static int enc_add_attr(MSFilter *f, void *arg) {
	const char *fmtp = (const char *)arg;
	struct EncState *s = (struct EncState *)f->data;
	if (strstr(fmtp, "ptime:")) set_ptime(s, atoi(fmtp + 6));

	return 0;
};

static int enc_add_fmtp(MSFilter *f, void *arg) {
	const char *fmtp = (const char *)arg;
	struct EncState *s = (struct EncState *)f->data;
	char tmp[16] = {0};
	if (fmtp_get_value(fmtp, "maxptime", tmp, sizeof(tmp))) {
		s->maxptime = atoi(tmp);
	}
	if (fmtp_get_value(fmtp, "ptime", tmp, sizeof(tmp))) {
		set_ptime(s, atoi(tmp));
	}
	return 0;
}

static int get_sr(BCTBX_UNUSED(MSFilter *f), void *arg) {
	*(int *)arg = 16000;
	return 0;
}

static int get_ptime(MSFilter *f, void *arg) {
	struct EncState *s = (struct EncState *)f->data;
	*((int *)arg) = s->ptime;
	return 0;
}

static MSFilterMethod enc_methods[] = {{MS_FILTER_ADD_ATTR, enc_add_attr},
                                       {MS_FILTER_ADD_FMTP, enc_add_fmtp},
                                       {MS_FILTER_GET_SAMPLE_RATE, get_sr},
                                       {MS_AUDIO_ENCODER_GET_PTIME, get_ptime},
                                       {0, NULL}};

#ifdef _MSC_VER

MSFilterDesc ms_g722_enc_desc = {MS_G722_ENC_ID,
                                 "MSG722Enc",
                                 "The G.722 wideband codec",
                                 MS_FILTER_ENCODER,
                                 "g722",
                                 1,
                                 1,
                                 enc_init,
                                 NULL,
                                 enc_process,
                                 NULL,
                                 enc_uninit,
                                 enc_methods};

#else

MSFilterDesc ms_g722_enc_desc = {.id = MS_G722_ENC_ID,
                                 .name = "MSG722Enc",
                                 .text = "The G.722 wideband codec",
                                 .category = MS_FILTER_ENCODER,
                                 .enc_fmt = "g722",
                                 .ninputs = 1,
                                 .noutputs = 1,
                                 .init = enc_init,
                                 .process = enc_process,
                                 .uninit = enc_uninit,
                                 .methods = enc_methods};
#endif

struct DecState {
	g722_decode_state_t *dec_state;
	g722_encode_state_t *enc_state;
	plc_context_t *plc_context;
	MSConcealerContext *concealer;
};

static void dec_init(MSFilter *f) {
	struct DecState *s = ms_new0(struct DecState, 1);
	f->data = s;
	s->dec_state = g722_decode_init(NULL, 64000, 0);
	s->enc_state = g722_encode_init(NULL, 64000, 0);
	s->concealer = ms_concealer_context_new(UINT32_MAX);
	s->plc_context = generic_plc_create_context(16000); /* g722 has a sample rate of 16kHz */
};

static void dec_uninit(MSFilter *f) {
	struct DecState *s = (struct DecState *)f->data;
	g722_decode_release(s->dec_state);
	g722_encode_release(s->enc_state);
	generic_plc_destroy_context(s->plc_context);
	ms_concealer_context_destroy(s->concealer);
	ms_free(f->data);
	f->data = 0;
};

static void dec_process(MSFilter *f) {
	struct DecState *s = (struct DecState *)f->data;
	mblk_t *im;
	mblk_t *om;

	while ((im = ms_queue_get(f->inputs[0]))) {
		size_t msg_size = msgdsize(im);
		int declen;
		uint8_t *reencoded_buffer = ms_malloc0(msg_size);

		om = allocb(msg_size * 4, 0);
		mblk_meta_copy(im, om);

		if ((declen = g722_decode(s->dec_state, (int16_t *)om->b_wptr, im->b_rptr, (int)msg_size)) < 0) {
			ms_warning("g722_decode error!");
			freemsg(om);
		} else {
			ms_concealer_inc_sample_time(
			    s->concealer, f->ticker->time, declen / 16,
			    TRUE); /* increase concealer context by sample nbr decoded/sample rate (in ms) */
			/* update local encoder state with received data, re-encoded buffer is just discarded */
			g722_encode(s->enc_state, reencoded_buffer, (int16_t *)om->b_wptr, declen);

			scale_up((int16_t *)om->b_wptr, declen);

			/* Store decoded data in plc_buffer */
			generic_plc_update_plc_buffer(s->plc_context, om->b_wptr, declen * sizeof(int16_t));

			/* introduce delay (TRANSITION_DELAY ms) */
			generic_plc_update_continuity_buffer(s->plc_context, om->b_wptr, declen * sizeof(int16_t));

			if (s->plc_context->plc_samples_used != 0) {
				/*we were doing PLC, now resuming with normal audio, continuity buffer is twice the transition delay
				 * lengths, the second half is untouched by the update function and contains transition data generated
				 * by PLC The first transitionBufferSize bytes of b_wptr have been written from continuity buffer,
				 * current incoming message starts at b_wptr+transitionBufferSize. However if the incoming message is
				 * shorter than transition delay, there is no data to mix as it was swallowed by the continuity buffer
				 * during generic_plc_update_continuity_buffer */
				if (declen >= 2 * 16 * TRANSITION_DELAY) {
					/* We have enough data to mix the late PLC data and new incoming audio */
					generic_plc_transition_mix(((int16_t *)(om->b_wptr)) + 16 * TRANSITION_DELAY,
					                           ((int16_t *)(s->plc_context->continuity_buffer)) + 16 * TRANSITION_DELAY,
					                           16 * TRANSITION_DELAY);
				} else {
					/* new incoming audio is not in b_wptr but at the begining of the continuity buffer, so we mix the
					 * end of it with its beginning */
					/* Note: if the received size is between 1 and 2 transition delay, we should mix part of the om and
					 * part of the continuity buffer This case is not relevant enough - it shall actually never be true
					 * - to bother, if this happens, we'll ear a crack sound < 5ms */
					generic_plc_transition_mix((int16_t *)(s->plc_context->continuity_buffer),
					                           (int16_t *)(s->plc_context->continuity_buffer + 16 * TRANSITION_DELAY),
					                           16 * TRANSITION_DELAY);
				}
			}
		}
		s->plc_context->plc_index = 0;
		s->plc_context->plc_samples_used = 0;

		om->b_wptr += declen * sizeof(int16_t);
		ms_queue_put(f->outputs[0], om);

		ms_free(reencoded_buffer);
		freemsg(im);
	}

	/* PLC needed ?*/
	if (ms_concealer_context_is_concealement_required(s->concealer, f->ticker->time)) {
		unsigned int buff_size =
		    16 * sizeof(int16_t) * f->ticker->interval; /* size in bytes of signal to be generated */
		unsigned char *reencoded_buffer = ms_malloc0(buff_size);
		unsigned char *decoded_buffer = ms_malloc0(buff_size);
		int k;

		ms_concealer_inc_sample_time(s->concealer, f->ticker->time, f->ticker->interval, FALSE);

		om = allocb(buff_size, 0);

		mblk_set_plc_flag(om, 1);
		generic_plc_generate_samples(s->plc_context, (int16_t *)om->b_wptr, (uint16_t)(buff_size / sizeof(int16_t)));
		/* store the generated samples into plc_buffer */
		generic_plc_update_plc_buffer(s->plc_context, om->b_wptr, buff_size);

		memcpy(decoded_buffer, om->b_wptr + TRANSITION_DELAY * 16 * sizeof(int16_t),
		       buff_size - TRANSITION_DELAY * 16 * sizeof(int16_t));
		memcpy(decoded_buffer + buff_size - TRANSITION_DELAY * 16 * sizeof(int16_t), s->plc_context->continuity_buffer,
		       TRANSITION_DELAY * 16 * sizeof(int16_t));

		/* update local encoder/decoder state with plc generated data, re-encoded buffer is just discarded */
		scale_down((int16_t *)decoded_buffer, 16 * f->ticker->interval);
		k = g722_encode(s->enc_state, reencoded_buffer, (int16_t *)decoded_buffer, 16 * f->ticker->interval);
		k = g722_decode(s->dec_state, (int16_t *)decoded_buffer, reencoded_buffer, k);
		ms_free(reencoded_buffer);
		ms_free(decoded_buffer);

		om->b_wptr += buff_size;
		ms_queue_put(f->outputs[0], om);
	}
};

static int ms_g722_dec_have_plc(BCTBX_UNUSED(MSFilter *f), void *arg) {
	*((int *)arg) = 1;
	return 0;
}

static MSFilterMethod dec_methods[] = {
    {MS_FILTER_GET_SAMPLE_RATE, get_sr}, {MS_AUDIO_DECODER_HAVE_PLC, ms_g722_dec_have_plc}, {0, NULL}};

#ifdef _MSC_VER

MSFilterDesc ms_g722_dec_desc = {MS_G722_DEC_ID,
                                 "MSG722Dec",
                                 "The G.722 wideband codec",
                                 MS_FILTER_DECODER,
                                 "g722",
                                 1,
                                 1,
                                 dec_init,
                                 NULL,
                                 dec_process,
                                 NULL,
                                 dec_uninit,
                                 dec_methods,
                                 MS_FILTER_IS_PUMP};

#else

MSFilterDesc ms_g722_dec_desc = {.id = MS_G722_DEC_ID,
                                 .name = "MSG722Dec",
                                 .text = "The G.722 wideband codec",
                                 .category = MS_FILTER_DECODER,
                                 .enc_fmt = "g722",
                                 .ninputs = 1,
                                 .noutputs = 1,
                                 .init = dec_init,
                                 .process = dec_process,
                                 .uninit = dec_uninit,
                                 .flags = MS_FILTER_IS_PUMP,
                                 .methods = dec_methods};

#endif

MS_FILTER_DESC_EXPORT(ms_g722_dec_desc)
MS_FILTER_DESC_EXPORT(ms_g722_enc_desc)
